using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Configuration;
using System.Configuration;
using System.ServiceModel;
using System.Diagnostics;
using System.ServiceModel.Channels;
using System.Xml;
using System.ComponentModel;
using System.Xml.Serialization;
using System.IO;
using System.Globalization;

namespace BizTalk.ESB.Extensions.WCFBehaviors
{

    public class CustomSoapHeader
    {
        private string _Name;
        public string Name { 
            get { return _Name;}
            set { _Name = value; }
        }

        private string _Namespace;
        public string Namespace {
            get { return _Namespace; }
            set { _Namespace = value; }
        }

        private bool _Required;
        public bool Required {
            get { return _Required; }
            set { _Required = value; }
        }

        private ContextAction _Action;
        public ContextAction Action {
            get { return _Action; }
            set { _Action = value; }
        }
    }

    public enum ContextAction
    {
        None = 0,
        Write = 1,
        Promote = 2
    }

    public class SOAPHeaderPromotion : BehaviorExtensionElement, IEndpointBehavior
    {
         #region BehaviorExtensionElement Methods

        public override Type BehaviorType
        {
            get { return typeof(SOAPHeaderPromotion); }
        }

        protected override object CreateBehavior()
        {
            return new SOAPHeaderPromotion(this.Headers);
        }

        //Copies the content of the specified configuration element to this configuration element
        public override void CopyFrom(ServiceModelExtensionElement extFrom)
        {
            base.CopyFrom(extFrom);
            SOAPHeaderPromotion element = extFrom as SOAPHeaderPromotion;
            if (element != null)
            {
                Headers = element.Headers;
            }
        }

        //Both properties are returned as a collection.
        protected override ConfigurationPropertyCollection Properties
        {
            get
            {
                if (_properties == null)
                {
                    _properties = new ConfigurationPropertyCollection();

                    _properties.Add(new ConfigurationProperty("Headers", typeof(List<CustomSoapHeader>), null,
                    new SerializationConverter(typeof(List<CustomSoapHeader>)), null, ConfigurationPropertyOptions.None));
                    base["Headers"] = new List<CustomSoapHeader>();
                }
                return _properties;
            }
        }

        #endregion

        #region IEndpointBehavior Members

        public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {
            bindingParameters.Add(this);
        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
        {
            //throw new NotImplementedException();
        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
        {
            endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new CustomHeaderMessageInspector());
        }

        public void Validate(ServiceEndpoint endpoint)
        {
            //throw new NotImplementedException();
        }

        #endregion

        #region Class properties
        [ConfigurationProperty("Headers")]
        public List<CustomSoapHeader> Headers
        {
            get
            {
                return (List<CustomSoapHeader>)base["Headers"];
            }
            set
            {
                base["Headers"] = value;
            }
        }

        #endregion

        #region Class Fields
        private ConfigurationPropertyCollection _properties;
        #endregion

        #region Constructors
        public SOAPHeaderPromotion(List<CustomSoapHeader> headers)
        {
            this.Headers = headers;
        }
        public SOAPHeaderPromotion() : base()
        {}

        #endregion

    }

    class CustomHeaderMessageInspector : IDispatchMessageInspector
    {
        #region IDispatchMessageInspector Members

        public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
        {
            List<KeyValuePair<XmlQualifiedName, object>> writeProps = new List<KeyValuePair<XmlQualifiedName, object>>();
            List<KeyValuePair<XmlQualifiedName, object>> promoteProps = new List<KeyValuePair<XmlQualifiedName, object>>();
            string writeKey = "http://schemas.microsoft.com/BizTalk/2006/01/Adapters/WCF-properties/WriteToContext";
            string promoteKey = "http://schemas.microsoft.com/BizTalk/2006/01/Adapters/WCF-properties/Promote";

            SOAPHeaderPromotion inboundHeaders = null;

            
            System.Diagnostics.Debug.WriteLine("*** SERVER - RECEIVED REQUEST ***") ;
            System.Diagnostics.Debug.WriteLine(request.ToString());

            if (instanceContext.Host.Description.Endpoints.Find(channel.LocalAddress.Uri) != null)
                inboundHeaders = instanceContext.Host.Description.Endpoints.Find(channel.LocalAddress.Uri).Behaviors.Find<SOAPHeaderPromotion>();

            if (inboundHeaders != null)
            {
                foreach (CustomSoapHeader header in inboundHeaders.Headers)
                {
                   
                    int headerPosition = OperationContext.Current.IncomingMessageHeaders.FindHeader(header.Name, header.Namespace);
                    
                    if (headerPosition < 0)
                    {
                        if (header.Required)
                        {
                            //Fault Condition
                            throw new ArgumentNullException(header.Name, "Required soap header not found.");
                        }
                    }
                    else
                    {
                        if (header.Action != ContextAction.None)
                        {
                            // Get an XmlDictionaryReader to read the header content
                            XmlDictionaryReader xmlDictionaryReader = OperationContext.Current.IncomingMessageHeaders.GetReaderAtHeader(headerPosition);
                            XmlDocument xmlDoc = new XmlDocument();

                            xmlDoc.LoadXml(xmlDictionaryReader.ReadOuterXml());
                            XmlQualifiedName PropName1 = new XmlQualifiedName(header.Name, header.Namespace);

                            if (header.Action == ContextAction.Write)
                            {
                                writeProps.Add(new KeyValuePair<XmlQualifiedName, object>(PropName1, xmlDoc.DocumentElement.InnerText));
                            }
                            else if (header.Action == ContextAction.Promote)
                            {
                                promoteProps.Add(new KeyValuePair<XmlQualifiedName, object>(PropName1, xmlDoc.DocumentElement.InnerText));
                            }
                        }
                    }
                }
            }
            else
            {
            }

            if (writeProps.Count > 0)
            {
                request.Properties[writeKey] = writeProps;
            }

            if (promoteProps.Count > 0)
            {
                request.Properties[promoteKey] = promoteProps;
            }

            return null;
        }

        public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
        {
        }

        #endregion
    }
    public class SerializationConverter : TypeConverter
    {
        private Type _type;
        public SerializationConverter(Type type)
        {
            _type = type;
        }

        #region Helper Utilities

        private object Deserialize(object value)
        {
            if (_type == null) throw new ArgumentNullException("", "Serialization type was not set. Use the SerializationConvertor(Type) constructor.");
            return Deserialize(value, _type);
        }

        private object Deserialize(object value, Type destinationType)
        {
            StringReader strRdr = null;
            XmlReader xmlRdr = null;

            try
            {
                if (!(value is string))
                    throw new ApplicationException("Expecting parameter 'value' to be of type string. 'Value' is of type " + value.GetType().ToString());

                XmlSerializer serializer = new XmlSerializer(destinationType);
                strRdr = new StringReader(value.ToString());
                xmlRdr = XmlReader.Create(strRdr);

                return serializer.Deserialize(xmlRdr);
            }
            finally
            {
                strRdr.Close();
                xmlRdr.Close();
            }
        }

        private object Serialize(object value)
        {
            StringWriter wtr = null;

            try
            {
                if (value == null)
                    throw new ArgumentNullException("value");

                StringBuilder sb = new StringBuilder();
                XmlSerializer serializer = new XmlSerializer(value.GetType());
                wtr = new StringWriter(sb);
                serializer.Serialize(wtr, value);

                return sb.ToString();
            }
            finally
            {
                wtr.Close();
            }
        }

        #endregion

        #region Collection code.
        //Class to string
        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
        {
            if (value is string)
            {
                return Deserialize(value);
            }
            else if (value.GetType() == _type)
            {
                return Serialize(value);
            }
            else
            {
                string msg = (_type == null ? "Value is not of type System.string and no type was specified at construction." : "Value is not of type System.string or " + _type.ToString() + ".");
                throw new ApplicationException(msg);
            }
        }

        //String to class
        public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
        {
            if (value is string)
            {
                return Deserialize(value, destinationType);
            }
            else if (value.GetType() == _type)
            {
                return Serialize(value);
            }
            else
            {
                string msg = (_type == null ? "Value is not of type System.string and no type was specified at construction." : "Value is not of type System.string or " + _type.ToString() + ".");
                throw new ApplicationException(msg);
            }
        }
        #endregion
    }
}
